The difference from the "official" definition is the addition of an extra
argument, which allows me to pass additional information to the routine. This
information is passed to the glue-generating routine, and bound into the
generated wrapper code, so GX doesn't need to know it is being passed! Here is the structure for holding the wrapper glue code: TYPE BoundgxSpoolProcedure = ARRAY [1 .. 13] OF ShortCard;And here is the routine that takes the address of your spool callback routine and the value of the extra argument, and actually generates the wrapper glue: |
PROCEDURE BindgxSpoolProcedure ( TheProc : gxSpoolProcedure; ToArg : ADDRESS; VAR Result : BoundgxSpoolProcedure ); (* creates a closure of the specified spool procedure which can be passed where QuickDraw GX expects one. *) BEGIN Result[1] := 041EFH; (* lea n(sp), a0 *) Result[2] := 00004H; (* n for above *) Result[3] := 042A7H; (* clr.l -(sp) *) (* room for result *) Result[4] := 02F18H; (* move.l (a0)+, -(sp) *) (* copy command arg *) Result[5] := 02F18H; (* move.l (a0)+, -(sp) *) (* copy block arg *) Result[6] := 02F3CH; (* move.l #n, -(sp) *) (* insert additional arg *) Result[7] := HiWrd(ToArg); (* n for above *) Result[8] := LoWrd(ToArg); (* ditto *) Result[9] := 04EB9H; (* jsr l *) Result[10] := HiWrd(CAST(LongCard, TheProc)); (* l for above *) Result[11] := LoWrd(CAST(LongCard, TheProc)); (* ditto *) Result[12] := 0201FH; (* move.l (sp)+, d0 *) (* return result where C expects it *) Result[13] := 04E75H; (* rts *) FlushCaches END BindgxSpoolProcedure;
As an example of how to use the above, here is my version of the HandleToShape routine from the Apple-provided GX library source code. Note in particular how the extra argument is used to pass the environment pointer to allow access to outer local variables from the inner routine: |
PROCEDURE HandleToShape ( TheHandle : Handle; NrViewPorts : LONGINT; ViewPorts : gxViewPortPtr (* array *) ) : gxShape; (* unflattens a handle into a new shape. Any graphics errors are automatically posted. *) VAR SrcOffset : LONGCARD; BoundSpoolFromHandle : BoundgxSpoolProcedure; HandleSpool : gxSpoolBlock; PROCEDURE SpoolFromHandle ( command : gxSpoolCommand; VAR block : gxSpoolBlock ) : LONGINT; (* spool routine to retrieve the flattened shape data from TheHandle. *) BEGIN SAVEREGS; CASE VAL(INTEGER, command) OF | GXGraphicsTypes.gxReadSpool: BlockMoveData ( (*sourcePtr :=*) TheHandle^ + SrcOffset, (*destPtr :=*) block.buffer, (*byteCount :=*) block.count ); SrcOffset := SrcOffset + VAL(LONGCARD, block.count) ELSE (* ignore *) END (*CASE*); RETURN 0 END SpoolFromHandle; BEGIN (*HandleToShape*) SrcOffset := 0; BindgxSpoolProcedure ( (*TheProc :=*) CAST(GXUseful.gxSpoolProcedure, ADR(SpoolFromHandle)), (*ToArg :=*) CurrentA6(), (*@Result :=*) BoundSpoolFromHandle ); HandleSpool.spoolProcedure := ADR(BoundSpoolFromHandle); HandleSpool.buffer := NIL; (* let GX allocate the buffer *) HandleSpool.bufferSize := 0; (* ditto *) RETURN GXUnflattenShape ( (*@block :=*) HandleSpool, (*count :=*) NrViewPorts, (*portList :=*) ViewPorts ) END HandleToShape;
Implementing a GX Extension or DriverWhen trying to write a GX printing extension or driver in a language that conforms to Pascal calling conventions, you have the job of generating appropriate glue for each of your message overrides. As I made clear in the previous section, doing this by hand, in assembly language, is not my idea of fun.Thus, I hit upon the idea of writing an MPW tool, called PrintingSegment, to automatically generate the glue code. The result takes the form of a .o object file that you link at the front of your "pext" or "pdvr" code segment. The same tool can also generate the "over" resources that tell GX printing where the routine entry points are and which messages they handle. Thus, the tool also solves the problem of keeping the "over" resources in sync. I did make one simplification: I decided that giving the tool the information necessary to copy and convert the argument list was too much work. Instead, I pass a single pointer to the argument list as the argument to the Pascal-conformant routine. For example, consider the interface for an override for the gxDespoolPage message. In Apple's C interfaces, this is defined as: typedef OSErr (*GXDespoolPageProcPtr) ( gxSpoolFile theSpoolFile, long numPages, gxFormat theFormat, gxShape *thePage, Boolean *formatChanged );The following record structure exactly mirrors the layout of the C argument list: TYPE BooleanPtr = POINTER TO BOOLEAN; gxShapePtr = POINTER TO gxShape; GXDespoolPageArgs = RECORD theSpoolFile : gxSpoolFile; PageNumber : LONGINT; theFormat : gxFormat; thePage : gxShapePtr; formatChanged : BooleanPtr END (*RECORD*);(Note that all the C arguments are passed as longwords.) So the Pascal-conformant override for this message is declared as: PROCEDURE HandleDespoolPage ( VAR Args : GXDespoolPageArgs ) : OSErr;and the glue that PrintingSegment generates to call this override looks like this: CLR.W -(A7) PEA $0006(A7) JSR HandleDespoolPage MOVE.W (A7)+,D0 RTSHere is what the body of HandleDespoolPage might look like, in a printing extension that allows the user to reverse the order in which pages are printed: PROCEDURE HandleDespoolPage ( VAR Args : GXDespoolPageArgs ) : OSErr; (* override for gxDespoolPage message. *) VAR ThisPageNumber : LONGINT; Err : OSErr; BEGIN SAVEREGS; IF ReverseOrderPrinting THEN ThisPageNumber := NrPages - Args.PageNumber + 1 ELSE ThisPageNumber := Args.PageNumber END (*IF*); Err := ForwardGXDespoolPage ( (*theSpoolFile :=*) Args.theSpoolFile, (*PageNumber :=*) ThisPageNumber, (*theFormat :=*) Args.theFormat, (*@thePage :=*) Args.thePage^, (*@formatChanged :=*) Args.formatChanged^ ); RETURN Err END HandleDespoolPage; SummaryIt is certainly feasible to write all kinds of GX-based code -- from straight applications to printer drivers and extensions -- in a high-level language rather than C.
Technotes Previous Technote | Contents | Next Technote |